<template>
  <div :class="getClass" :style="getWrapperStyle">
    <img
      v-show="isReady"
      ref="imgElRef"
      :src="src"
      :alt="alt"
      :crossorigin="crossorigin"
      :style="getImageStyle"
    />
  </div>
</template>
<script lang="ts">
import type { CSSProperties } from "vue";
import { defineComponent, onMounted, ref, unref, computed, onUnmounted } from "vue";
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
import { useDesign } from "/@/hooks/web/UseDesign";
import { useDebounceFn } from "@vueuse/shared";

type Options = Cropper.Options;

const defaultOptions: Options = {
  aspectRatio: 1,
  zoomable: true,
  zoomOnTouch: true,
  zoomOnWheel: true,
  cropBoxMovable: true,
  cropBoxResizable: true,
  toggleDragModeOnDblclick: true,
  autoCrop: true,
  background: true,
  highlight: true,
  center: true,
  responsive: true,
  restore: true,
  checkCrossOrigin: true,
  checkOrientation: true,
  scalable: true,
  modal: true,
  guides: true,
  movable: true,
  rotatable: true
};

const props = {
  src: { type: String, required: true },
  alt: { type: String },
  circled: { type: Boolean, default: false },
  realTimePreview: { type: Boolean, default: true },
  height: { type: [String, Number], default: "360px" },
  crossorigin: {
    type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>,
    default: undefined
  },
  imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
  options: { type: Object as PropType<Options>, default: () => ({}) }
};

export default defineComponent({
  name: "CropperImage",
  props,
  emits: ["cropend", "ready", "cropendError"],
  setup(props, { attrs, emit }) {
    const imgElRef = ref<ElRef<HTMLImageElement>>();
    const cropper = ref<Nullable<Cropper>>();
    const isReady = ref(false);

    const { prefixCls } = useDesign("cropper-image");
    const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);

    const getImageStyle = computed((): CSSProperties => {
      return {
        height: props.height,
        maxWidth: "100%",
        ...props.imageStyle
      };
    });

    const getClass = computed(() => {
      return [
        prefixCls,
        attrs.class,
        {
          [`${prefixCls}--circled`]: props.circled
        }
      ];
    });

    const getWrapperStyle = computed((): CSSProperties => {
      return { height: `${props.height}`.replace(/px/, "") + "px" };
    });

    onMounted(init);

    onUnmounted(() => {
      cropper.value?.destroy();
    });

    async function init() {
      const imgEl = unref(imgElRef);
      if (!imgEl) {
        return;
      }
      cropper.value = new Cropper(imgEl, {
        ...defaultOptions,
        ready: () => {
          isReady.value = true;
          realTimeCroppered();
          emit("ready", cropper.value);
        },
        crop() {
          debounceRealTimeCroppered();
        },
        zoom() {
          debounceRealTimeCroppered();
        },
        cropmove() {
          debounceRealTimeCroppered();
        },
        ...props.options
      });
    }

    // Real-time display preview
    function realTimeCroppered() {
      props.realTimePreview && croppered();
    }

    // event: return base64 and width and height information after cropping
    function croppered() {
      if (!cropper.value) {
        return;
      }
      let imgInfo = cropper.value.getData();
      const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
      canvas.toBlob((blob) => {
        if (!blob) {
          return;
        }
        let fileReader: FileReader = new FileReader();
        fileReader.readAsDataURL(blob);
        fileReader.onloadend = (e) => {
          emit("cropend", {
            imgBase64: e.target?.result ?? "",
            imgInfo
          });
        };
        fileReader.onerror = () => {
          emit("cropendError");
        };
      }, "image/png");
    }

    // Get a circular picture canvas
    function getRoundedCanvas() {
      const sourceCanvas = cropper.value!.getCroppedCanvas();
      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d")!;
      const width = sourceCanvas.width;
      const height = sourceCanvas.height;
      canvas.width = width;
      canvas.height = height;
      context.imageSmoothingEnabled = true;
      context.drawImage(sourceCanvas, 0, 0, width, height);
      context.globalCompositeOperation = "destination-in";
      context.beginPath();
      context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
      context.fill();
      return canvas;
    }

    return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
  }
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image';

.@{prefix-cls} {
  &--circled {
    .cropper-view-box,
    .cropper-face {
      border-radius: 50%;
    }
  }
}
</style>
